// Copyright (C) 2002 Andrew Tridgell
// Copyright (C) 2009-2020 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

#include "args.hpp"

#include "legacy_util.hpp"

struct args*
args_init(int init_argc, const char* const* init_args)
{
  struct args* args = (struct args*)x_malloc(sizeof(struct args));
  args->argc = 0;
  args->argv = (char**)x_malloc(sizeof(char*));
  args->argv[0] = nullptr;
  for (int i = 0; i < init_argc; i++) {
    args_add(args, init_args[i]);
  }
  return args;
}

struct args*
args_init_from_string(const char* command)
{
  char* p = x_strdup(command);
  char* q = p;
  char *word, *saveptr = nullptr;
  struct args* args = args_init(0, nullptr);
  while ((word = strtok_r(q, " \t\r\n", &saveptr))) {
    args_add(args, word);
    q = nullptr;
  }

  free(p);
  return args;
}

struct args*
args_init_from_gcc_atfile(const char* filename)
{
  char* argtext;
  if (!(argtext = read_text_file(filename, 0))) {
    return nullptr;
  }

  struct args* args = args_init(0, nullptr);
  char* pos = argtext;
  char* argbuf = static_cast<char*>(x_malloc(strlen(argtext) + 1));
  char* argpos = argbuf;

  // Used to track quoting state; if \0, we are not inside quotes. Otherwise
  // stores the quoting character that started it, for matching the end quote.
  char quoting = '\0';

  while (1) {
    switch (*pos) {
    case '\\':
      pos++;
      if (*pos == '\0') {
        continue;
      }
      break;

    case '\"':
    case '\'':
      if (quoting != '\0') {
        if (quoting == *pos) {
          quoting = '\0';
          pos++;
          continue;
        } else {
          break;
        }
      } else {
        quoting = *pos;
        pos++;
        continue;
      }

    case '\n':
    case '\r':
    case '\t':
    case ' ':
      if (quoting) {
        break;
      }
      // Fall through.

    case '\0':
      // End of token
      *argpos = '\0';
      if (argbuf[0] != '\0') {
        args_add(args, argbuf);
      }
      argpos = argbuf;
      if (*pos == '\0') {
        goto out;
      } else {
        pos++;
        continue;
      }
    }

    *argpos = *pos;
    pos++;
    argpos++;
  }

out:
  free(argbuf);
  free(argtext);
  return args;
}

struct args*
args_copy(struct args* args)
{
  return args_init(args->argc, args->argv);
}

// Insert all arguments in src into dest at position index. If replace is true,
// the element at dest->argv[index] is replaced with the contents of src and
// everything past it is shifted. Otherwise, dest->argv[index] is also shifted.
//
// src is consumed by this operation and should not be freed or used again by
// the caller.
void
args_insert(struct args* dest, int index, struct args* src, bool replace)
{
  // Adjustments made if we are replacing or shifting the element currently at
  // dest->argv[index].
  int offset = replace ? 1 : 0;

  if (replace) {
    free(dest->argv[index]);
  }

  if (src->argc == 0) {
    if (replace) {
      // Have to shift everything down by 1 since we replaced with an empty
      // list.
      for (int i = index; i < dest->argc; i++) {
        dest->argv[i] = dest->argv[i + 1];
      }
      dest->argc--;
    }
    args_free(src);
    return;
  }

  if (src->argc == 1 && replace) {
    // Trivial case; replace with 1 element.
    dest->argv[index] = src->argv[0];
    src->argc = 0;
    args_free(src);
    return;
  }

  dest->argv = (char**)x_realloc(
    dest->argv, (src->argc + dest->argc + 1 - offset) * sizeof(char*));

  // Shift arguments over.
  for (int i = dest->argc; i >= index + offset; i--) {
    dest->argv[i + src->argc - offset] = dest->argv[i];
  }

  // Copy the new arguments into place.
  for (int i = 0; i < src->argc; i++) {
    dest->argv[i + index] = src->argv[i];
  }

  dest->argc += src->argc - offset;
  src->argc = 0;
  args_free(src);
}

void
args_free(struct args* args)
{
  if (!args) {
    return;
  }
  for (int i = 0; i < args->argc; ++i) {
    if (args->argv[i]) {
      free(args->argv[i]);
    }
  }
  free(args->argv);
  free(args);
}

void
args_add(struct args* args, const char* s)
{
  args->argv = (char**)x_realloc(args->argv, (args->argc + 2) * sizeof(char*));
  args->argv[args->argc] = x_strdup(s);
  args->argc++;
  args->argv[args->argc] = nullptr;
}

// Add all arguments in to_append to args.
void
args_extend(struct args* args, struct args* to_append)
{
  for (int i = 0; i < to_append->argc; i++) {
    args_add(args, to_append->argv[i]);
  }
}

// Pop the last element off the args list.
void
args_pop(struct args* args, int n)
{
  while (n--) {
    args->argc--;
    free(args->argv[args->argc]);
    args->argv[args->argc] = nullptr;
  }
}

// Set argument at given index.
void
args_set(struct args* args, int index, const char* value)
{
  assert(index < args->argc);
  free(args->argv[index]);
  args->argv[index] = x_strdup(value);
}

// Remove the first element of the argument list.
void
args_remove_first(struct args* args)
{
  free(args->argv[0]);
  memmove(&args->argv[0], &args->argv[1], args->argc * sizeof(args->argv[0]));
  args->argc--;
}

// Add an argument into the front of the argument list.
void
args_add_prefix(struct args* args, const char* s)
{
  args->argv = (char**)x_realloc(args->argv, (args->argc + 2) * sizeof(char*));
  memmove(
    &args->argv[1], &args->argv[0], (args->argc + 1) * sizeof(args->argv[0]));
  args->argv[0] = x_strdup(s);
  args->argc++;
}

// Strip any arguments beginning with the specified prefix.
void
args_strip(struct args* args, const char* prefix)
{
  for (int i = 0; i < args->argc;) {
    if (str_startswith(args->argv[i], prefix)) {
      free(args->argv[i]);
      memmove(&args->argv[i],
              &args->argv[i + 1],
              (args->argc - i) * sizeof(args->argv[i]));
      args->argc--;
    } else {
      i++;
    }
  }
}

// Format args to a space-separated string. Does not quote spaces. Caller
// frees.
char*
args_to_string(const struct args* args)
{
  unsigned size = 0;
  for (char** p = args->argv; *p; p++) {
    size += strlen(*p) + 1;
  }

  char* result = static_cast<char*>(x_malloc(size + 1));
  int pos = 0;
  for (char** p = args->argv; *p; p++) {
    pos += sprintf(&result[pos], "%s ", *p);
  }
  result[pos - 1] = '\0';
  return result;
}

// Returns true if args1 equals args2, else false.
bool
args_equal(const struct args* args1, const struct args* args2)
{
  if (args1->argc != args2->argc) {
    return false;
  }
  for (int i = 0; i < args1->argc; i++) {
    if (!str_eq(args1->argv[i], args2->argv[i])) {
      return false;
    }
  }
  return true;
}

void
args_deleter::operator()(struct args*& arg)
{
  args_free(arg);
  arg = nullptr;
}
